 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.editors.text;

 import java.util.HashMap ;
 import java.util.Iterator ;
 import java.util.Map ;
 import java.util.StringTokenizer ;
 import java.util.Map.Entry;

 import org.eclipse.swt.SWT;
 import org.eclipse.swt.widgets.Shell;

 import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.IAdaptable;
 import org.eclipse.core.runtime.NullProgressMonitor;

 import org.eclipse.jface.action.Action;
 import org.eclipse.jface.internal.text.html.HTMLTextPresenter;
 import org.eclipse.jface.preference.IPreferenceStore;

 import org.eclipse.jface.text.DefaultInformationControl;
 import org.eclipse.jface.text.DefaultTextHover;
 import org.eclipse.jface.text.IInformationControl;
 import org.eclipse.jface.text.IInformationControlCreator;
 import org.eclipse.jface.text.ITextHover;
 import org.eclipse.jface.text.ITextHoverExtension;
 import org.eclipse.jface.text.ITextViewerExtension2;
 import org.eclipse.jface.text.IUndoManager;
 import org.eclipse.jface.text.TextViewerUndoManager;
 import org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter;
 import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
 import org.eclipse.jface.text.hyperlink.IHyperlinkPresenter;
 import org.eclipse.jface.text.quickassist.IQuickAssistAssistant;
 import org.eclipse.jface.text.quickassist.QuickAssistAssistant;
 import org.eclipse.jface.text.reconciler.IReconciler;
 import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
 import org.eclipse.jface.text.reconciler.MonoReconciler;
 import org.eclipse.jface.text.source.Annotation;
 import org.eclipse.jface.text.source.DefaultAnnotationHover;
 import org.eclipse.jface.text.source.IAnnotationHover;
 import org.eclipse.jface.text.source.ISourceViewer;
 import org.eclipse.jface.text.source.SourceViewerConfiguration;

 import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
 import org.eclipse.ui.texteditor.AnnotationPreference;
 import org.eclipse.ui.texteditor.HyperlinkDetectorRegistry;
 import org.eclipse.ui.texteditor.spelling.SpellingCorrectionProcessor;
 import org.eclipse.ui.texteditor.spelling.SpellingReconcileStrategy;
 import org.eclipse.ui.texteditor.spelling.SpellingService;


 /**
  * Source viewer configuration for the text editor.
  *
  * @since 3.0
  */
 public class TextSourceViewerConfiguration extends SourceViewerConfiguration {

     /**
      * The preference store used to initialize this configuration.
      * <p>
      * Note: protected since 3.1
      * </p>
      */
     protected IPreferenceStore fPreferenceStore;

     /**
      * Creates a text source viewer configuration.
      */
     public TextSourceViewerConfiguration() {
     }

     /**
      * Creates a text source viewer configuration and
      * initializes it with the given preference store.
      *
      * @param preferenceStore the preference store used to initialize this configuration
      */
     public TextSourceViewerConfiguration(IPreferenceStore preferenceStore) {
         fPreferenceStore= preferenceStore;
     }

     /*
      * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getAnnotationHover(org.eclipse.jface.text.source.ISourceViewer)
      */
     public IAnnotationHover getAnnotationHover(ISourceViewer sourceViewer) {
         return new DefaultAnnotationHover() {
             protected boolean isIncluded(Annotation annotation) {
                 return isShowInVerticalRuler(annotation);
             }
         };
     }

     /*
      * @see DefaultAnnotationHover#isIncluded(Annotation)
      * @since 3.2
      */
     protected boolean isShowInVerticalRuler(Annotation annotation) {
         AnnotationPreference preference= getAnnotationPreference(annotation);
         if (preference == null)
             return true;
         String key= preference.getVerticalRulerPreferenceKey();
         // backward compatibility
 if (key != null && !fPreferenceStore.getBoolean(key))
             return false;
         
         return true;
     }

     /*
      * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getOverviewRulerAnnotationHover(org.eclipse.jface.text.source.ISourceViewer)
      * @since 3.2
      */
     public IAnnotationHover getOverviewRulerAnnotationHover(ISourceViewer sourceViewer) {
         return new DefaultAnnotationHover() {
             protected boolean isIncluded(Annotation annotation) {
                 return isShowInOverviewRuler(annotation);
             }
         };
     }

     /*
      * @see DefaultAnnotationHover#isIncluded(Annotation)
      * @since 3.2
      */
     protected boolean isShowInOverviewRuler(Annotation annotation) {
         AnnotationPreference preference= getAnnotationPreference(annotation);
         if (preference == null)
             return true;
         String key= preference.getOverviewRulerPreferenceKey();
         if (key == null || !fPreferenceStore.getBoolean(key))
             return false;
         
         return true;
     }

     /*
      * @see SourceViewerConfiguration#getConfiguredTextHoverStateMasks(ISourceViewer, String)
      * @since 3.2
      */
     public int[] getConfiguredTextHoverStateMasks(ISourceViewer sourceViewer, String contentType) {
         return new int[] { ITextViewerExtension2.DEFAULT_HOVER_STATE_MASK };
     }

     /*
      * @see SourceViewerConfiguration#getTextHover(ISourceViewer, String)
      * @since 3.2
      */
     public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) {
         return new TextHover(sourceViewer);
     }

     /*
      * @see DefaultTextHover#isIncluded(Annotation)
      * @since 3.2
      */
     protected boolean isShownInText(Annotation annotation) {
         AnnotationPreference preference= getAnnotationPreference(annotation);
         if (preference == null)
             return false;
         String key= preference.getTextPreferenceKey();
         if (key != null) {
             if (!fPreferenceStore.getBoolean(key))
                 return false;
         } else {
             key= preference.getHighlightPreferenceKey();
             if (key == null || !fPreferenceStore.getBoolean(key))
                 return false;
         }
         return true;
     }
     
     /**
      * Returns the annotation preference for the given annotation.
      *
      * @param annotation the annotation
      * @return the annotation preference or <code>null</code> if none
      * @since 3.2
      */
     private AnnotationPreference getAnnotationPreference(Annotation annotation) {
         if (annotation == null || fPreferenceStore == null)
             return null;
         return EditorsUI.getAnnotationPreferenceLookup().getAnnotationPreference(annotation);
     }

     /*
      * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getTabWidth(org.eclipse.jface.text.source.ISourceViewer)
      */
     public int getTabWidth(ISourceViewer sourceViewer) {
         if (fPreferenceStore == null)
             return super.getTabWidth(sourceViewer);
         return fPreferenceStore.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
     }

     /*
      * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getIndentPrefixes(org.eclipse.jface.text.source.ISourceViewer, java.lang.String)
      * @since 3.3
      */
     public String [] getIndentPrefixes(ISourceViewer sourceViewer, String contentType) {
         String [] indentPrefixes= getIndentPrefixesForTab(getTabWidth(sourceViewer));
         if (indentPrefixes == null)
             return null;

         int length= indentPrefixes.length;
         if (length > 2 && fPreferenceStore != null && fPreferenceStore.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS)) {
             // Swap first with second last
 String first= indentPrefixes[0];
             indentPrefixes[0]= indentPrefixes[length - 2];
             indentPrefixes[length - 2]= first;
         }

         return indentPrefixes;
     }

     /*
      * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getHyperlinkDetectors(org.eclipse.jface.text.source.ISourceViewer)
      * @since 3.1
      */
     public IHyperlinkDetector[] getHyperlinkDetectors(ISourceViewer sourceViewer) {
         if (sourceViewer == null || fPreferenceStore == null)
             return super.getHyperlinkDetectors(sourceViewer);

         if (!fPreferenceStore.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINKS_ENABLED))
             return null;
         
         return getRegisteredHyperlinkDetectors(sourceViewer);
     }

     /**
      * Returns the registered hyperlink detectors which are used to detect
      * hyperlinks in the given source viewer.
      *
      * @param sourceViewer the source viewer to be configured by this configuration
      * @return an array with hyperlink detectors or <code>null</code> if no hyperlink detectors are registered
      * @since 3.3
      */
     protected final IHyperlinkDetector[] getRegisteredHyperlinkDetectors(ISourceViewer sourceViewer) {
         HyperlinkDetectorRegistry registry= EditorsUI.getHyperlinkDetectorRegistry();

         Map targets= getHyperlinkDetectorTargets(sourceViewer);
         Assert.isNotNull(targets);
         
         IHyperlinkDetector[] result= null;
         Iterator iter= targets.entrySet().iterator();
         while (iter.hasNext()) {
             Entry target= (Entry)iter.next();
             String targetId= (String )target.getKey();
             IAdaptable context= (IAdaptable)target.getValue();
             result= merge(result, registry.createHyperlinkDetectors(targetId, context));
         }
         return result;
     }

     /**
      * Returns the hyperlink detector targets supported by the
      * given source viewer.
      * <p>
      * Subclasses are allowed to modify the returned map.
      * </p>
      *
      * @param sourceViewer the source viewer to be configured by this configuration
      * @return the hyperlink detector targets with target id (<code>String</code>) as key
      * and the target context (<code>IAdaptable</code>) as value
      * @since 3.3
      */
     protected Map getHyperlinkDetectorTargets(ISourceViewer sourceViewer) {
         Map targets= new HashMap ();
         targets.put("org.eclipse.ui.DefaultTextEditor", null); //$NON-NLS-1$
 return targets;
     }

     /*
      * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getHyperlinkStateMask(org.eclipse.jface.text.source.ISourceViewer)
      * @since 3.1
      */
     public int getHyperlinkStateMask(ISourceViewer sourceViewer) {
         if (fPreferenceStore == null)
             return super.getHyperlinkStateMask(sourceViewer);

         String modifiers= fPreferenceStore.getString(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_KEY_MODIFIER);
         int modifierMask= computeStateMask(modifiers);
         if (modifierMask == -1) {
             // Fall back to stored state mask
 modifierMask= fPreferenceStore.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_KEY_MODIFIER_MASK);
         }
         return modifierMask;
     }

     /*
      * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getHyperlinkPresenter(org.eclipse.jface.text.source.ISourceViewer)
      * @since 3.1
      */
     public IHyperlinkPresenter getHyperlinkPresenter(ISourceViewer sourceViewer) {
         if (fPreferenceStore == null)
             return super.getHyperlinkPresenter(sourceViewer);

         return new DefaultHyperlinkPresenter(fPreferenceStore);
     }

     /**
      * Maps the localized modifier name to a code in the same
      * manner as #findModifier.
      *
      * @param modifierName the modifier name
      * @return the SWT modifier bit, or <code>0</code> if no match was found
      * @since 3.1
      */
     protected static final int findLocalizedModifier(String modifierName) {
         if (modifierName == null)
             return 0;

         if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.CTRL)))
             return SWT.CTRL;
         if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.SHIFT)))
             return SWT.SHIFT;
         if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.ALT)))
             return SWT.ALT;
         if (modifierName.equalsIgnoreCase(Action.findModifierString(SWT.COMMAND)))
             return SWT.COMMAND;

         return 0;
     }

     /**
      * Computes the state mask out of the given modifiers string.
      *
      * @param modifiers a string containing modifiers
      * @return the state mask
      * @since 3.1
      */
     protected static final int computeStateMask(String modifiers) {
         if (modifiers == null)
             return -1;

         if (modifiers.length() == 0)
             return SWT.NONE;

         int stateMask= 0;
         StringTokenizer modifierTokenizer= new StringTokenizer (modifiers, ",;.:+-* "); //$NON-NLS-1$
 while (modifierTokenizer.hasMoreTokens()) {
             int modifier= findLocalizedModifier(modifierTokenizer.nextToken());
             if (modifier == 0 || (stateMask & modifier) == modifier)
                 return -1;
             stateMask= stateMask | modifier;
         }
         return stateMask;
     }

     /*
      * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getUndoManager(org.eclipse.jface.text.source.ISourceViewer)
      * @since 3.1
      */
     public IUndoManager getUndoManager(ISourceViewer sourceViewer) {
         if (fPreferenceStore == null || !fPreferenceStore.contains(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_UNDO_HISTORY_SIZE))
             return super.getUndoManager(sourceViewer);

         int undoHistorySize= fPreferenceStore.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_UNDO_HISTORY_SIZE);
         return new TextViewerUndoManager(undoHistorySize);
     }

     /**
      * Returns the reconciler ready to be used with the given source viewer.
      * <p>
      * This implementation currently returns a {@link MonoReconciler} which
      * is responsible for spell checking. In the future a different reconciler
      * taking over more responsibilities might be returned.</p>
      *
      * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getReconciler(org.eclipse.jface.text.source.ISourceViewer)
      * @since 3.3
      */
     public IReconciler getReconciler(ISourceViewer sourceViewer) {
         if (fPreferenceStore == null || !fPreferenceStore.getBoolean(SpellingService.PREFERENCE_SPELLING_ENABLED))
             return null;

         SpellingService spellingService= EditorsUI.getSpellingService();
         if (spellingService.getActiveSpellingEngineDescriptor(fPreferenceStore) == null)
             return null;
         
         IReconcilingStrategy strategy= new SpellingReconcileStrategy(sourceViewer, spellingService);
         MonoReconciler reconciler= new MonoReconciler(strategy, false);
         reconciler.setIsIncrementalReconciler(false);
         reconciler.setProgressMonitor(new NullProgressMonitor());
         reconciler.setDelay(500);
         return reconciler;
     }

     /*
      * @see org.eclipse.jface.text.source.SourceViewerConfiguration#getQuickAssistAssistant(org.eclipse.jface.text.source.ISourceViewer)
      * @since 3.3
      */
     public IQuickAssistAssistant getQuickAssistAssistant(ISourceViewer sourceViewer) {
         if (fPreferenceStore == null || !fPreferenceStore.getBoolean(SpellingService.PREFERENCE_SPELLING_ENABLED))
             return null;

         IQuickAssistAssistant assistant= new QuickAssistAssistant();
         assistant.setQuickAssistProcessor(new SpellingCorrectionProcessor());
         assistant.setInformationControlCreator(getQuickAssistAssistantInformationControlCreator());
         
         // Waiting for color preferences, see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=133731
 assistant.setProposalSelectorBackground(sourceViewer.getTextWidget().getDisplay().getSystemColor(SWT.COLOR_WHITE));
         assistant.setProposalSelectorForeground(sourceViewer.getTextWidget().getDisplay().getSystemColor(SWT.COLOR_BLACK));
         
         return assistant;
     }

     /**
      * Returns the information control creator for the quick assist assistant.
      *
      * @return the information control creator
      * @since 3.3
      */
     private IInformationControlCreator getQuickAssistAssistantInformationControlCreator() {
         return new IInformationControlCreator() {
             public IInformationControl createInformationControl(Shell parent) {
                 return new DefaultInformationControl(parent, SWT.NONE, new HTMLTextPresenter(true));
             }
         };
     }

     /**
      * Helper method to merge two {@link IHyperlinkDetector} arrays.
      *
      * @param array1 an array of hyperlink detectors or <code>null</code>
      * @param array2 an array of hyperlink detectors or <code>null</code>
      * @return an array with the merged hyperlink detectors or <code>null</code> if both given arrays are <code>null</code>
      * @since 3.3
      */
     private IHyperlinkDetector[] merge(IHyperlinkDetector[] array1, IHyperlinkDetector[] array2) {
         if (array1 == null && array2 == null)
             return null;
         else if (array1 == null)
             return array2;
         else if (array2 == null)
             return array1;
         else {
             IHyperlinkDetector[] allHyperlinkDetectors;
             int size= array1.length + array2.length;
             allHyperlinkDetectors= new IHyperlinkDetector[size];
             System.arraycopy(array1, 0, allHyperlinkDetectors, 0, array1.length);
             System.arraycopy(array2, 0, allHyperlinkDetectors, array1.length, array2.length);
             return allHyperlinkDetectors;
         }
     }

     /**
      * Text hover with custom control creator that
      * can show the tool tip affordance.
      *
      * @since 3.3
      */
     private final class TextHover extends DefaultTextHover implements ITextHoverExtension {
         public TextHover(ISourceViewer sourceViewer) {
             super(sourceViewer);
         }

         protected boolean isIncluded(Annotation annotation) {
             return isShownInText(annotation);
         }

         /*
          * @see org.eclipse.jface.text.ITextHoverExtension#getHoverControlCreator()
          */
         public IInformationControlCreator getHoverControlCreator() {
             return new IInformationControlCreator() {
                 public IInformationControl createInformationControl(Shell parent) {
                     return new DefaultInformationControl(parent, SWT.NONE, new HTMLTextPresenter(true), EditorsUI.getTooltipAffordanceString());
                 }
             };
         }
     }

 }

